from __future__ import absolute_import, print_function, unicode_literals, division
from future_builtins import * #@UnusedWildImport
if __name__ == '__main__':
    __package__ = b'pyson.schema' #@ReservedAssignment
    import pyson.schema #@UnusedImport

from pyson import types
from .schema_class import Schema
from .util import get_schemas
from .errors import AdaptationError

class Numeric(Schema):
    '''Base schema for all numeric values (including complex, Fraction, etc)'''

    BASE_T = types.Numeric
    ADAPT = lambda cls, x: x

    def __init__(self, *args, **kwds):
        minN, maxN = kwds.pop('min', None), kwds.pop('max', None)
        if not isinstance(minN, (self.BASE_T, type(None))):
            raise TypeError('`min` must be of the correct numeric type, got: %s' % type(minN))
        if not isinstance(maxN, (self.BASE_T, type(None))):
            raise TypeError('`min` must be of the correct numeric type, got: %s' % type(maxN))
        self.min, self.max = minN, maxN
        minN, maxN = self.bounds
        if minN >= maxN:
            raise ValueError('invalid bound: [%s, %s]' % (minN, maxN))

        super(Numeric, self).__init__(*args, **kwds)

    def isvalid(self, obj, **kwds):
        if not isinstance(obj, self.BASE_T):
            return False
        try:
            if ((self.min is not None) and obj < self.min) or ((self.max is not None) and obj > self.max):
                return False
        except TypeError:
            return False
        return True

    def adapt(self, obj, truncrange=False, **kwds):
        # Check type
        result = super(Numeric, self).adapt(obj, bypasscheck=True, **kwds)
        if not isinstance(result, self.BASE_T):
            if isinstance(result, types.Numeric):
                result = self.ADAPT(result)
            else:
                raise AdaptationError('not a numeric type: %s' % type(obj))

        # Check bounds
        minb, maxb = self.bounds
        try:
            if result < minb:
                if truncrange:
                    return minb
            elif result > maxb:
                if truncrange:
                    return maxb
            else:
                return result
        except TypeError:
            raise AdaptationError('no ordering relation: %s' % type(obj))
        else:
            raise AdaptationError('out of bounds: %s' % result)

    def _adapt_from_string(self, st, **kwds):
        try:
            return self.ADAPT(st)
        except:
            raise AdaptationError('cannot convert string to numeric type: %s' % repr(st))

    @property
    def bounds(self):
        return (self.min if self.min is not None else float('-inf'),
                self.max if self.max is not None else float('inf'))


class Number(Numeric):
    '''
    Validates float, Decimal or integer numeric types.
    '''

    BASE_T = types.Number

    @classmethod
    def ADAPT(cls, x):
        fx = float(x)
        try:
            ix = int(x)
        except TypeError:
            ix = None

        if fx == ix:
            return ix
        else:
            return fx

class Integer(Numeric):
    '''
    Validates integer types.
    '''

    BASE_T = types.Integer
    ADAPT = int

Int = Integer

class Float(Numeric):
    '''
    Validates float types.
    '''
    BASE_T = types.Float
    ADAPT = float

class Bool(Schema):
    '''
    Validate boolean objects.
    '''

    def isvalid(self, obj, **kwds):
        return isinstance(obj, types.Bool)

#    CONVERSIONS = { 'true': True, 'false': False, '1': True, '0': False }
#    @classmethod
#    def obj_from_string(cls, obj):
#        try:
#            return { 'true': True, 'false': False }[obj.lower()]
#        except KeyError:
#            raise ValueError("invalid boolean: '%s'" % obj)
#
#    def adapt(self, obj, inplace=False, path=[]):
#        if not self.is_valid(obj):
#            if isinstance(obj, basestring):
#                try:
#                    return Bool.CONVERSIONS[obj.lower()]
#                except KeyError:
#                    raise errors.AdaptationError((None, path))
#        else:
#            return obj

__all__ = get_schemas(globals())
if __name__ == '__main__':
    import doctest
    doctest.testmod(optionflags=doctest.REPORT_ONLY_FIRST_FAILURE, verbose=0)
